// Trait which will be mixed in all the connections or connected clients
package eventfax.core

import java.io.IOException
import java.nio._
import java.net._
import java.nio.channels.{SelectionKey,Selector,ServerSocketChannel,SocketChannel}
import java.nio.charset.{CharacterCodingException,Charset,CharsetDecoder}
import scala.collection.jcl.LinkedList


trait Connection {
  var clientChannel: SocketChannel = null
  var outBoundData: LinkedList[ByteBuffer] = new LinkedList()

  var readSelectionKey: SelectionKey = null
  var writeChannelKey: SelectionKey = null
  var client_connected: Boolean = true
  // private var selector: Selector = null
  var reactor: ReactorCore = null
  // binding string can be used as unique key identifying each connected client
  var bindingStr: String = null

  def decorate_handler(client: SocketChannel,key: SelectionKey,reactor: ReactorCore,bind_str: String) {
    this.clientChannel = client
    this.readSelectionKey = key
    this.reactor = reactor
    this.bindingStr = bind_str
  }

  def close_connection() {
    readSelectionKey.cancel()
    if(writeChannelKey != null ) writeChannelKey.cancel()
    clientChannel.close()
    unbind()
    reactor.activeConnections.removeKey(bindingStr)
  }

  def cancel_write() =  if(writeChannelKey != null ) writeChannelKey.cancel()

  def receive_data(data: FaxData): Unit 

  def connect(host: String,port: Int)  = {}

  def unbind(): Unit 

  def post_init(): Unit = {}

  def on_write_error()

  def send_data(faxData: FaxData): Unit = {
    var buffer: ByteBuffer = faxData.buffer()
    buffer.flip()
    outBoundData.add(buffer)
    write_and_schedule()
  }

  def write_and_schedule(): Unit = {
    var socket_buffer_full = false
    while(!outBoundData.isEmpty && !socket_buffer_full && client_connected)
    {
      var list_item: ByteBuffer = outBoundData(0)
      
      try { clientChannel.write(list_item) } 
      catch { case ex: IOException => client_connected = false }

      if(list_item.remaining == 0) outBoundData.remove(0)
      else socket_buffer_full = true
    }
    if(!client_connected) on_write_error()
    else if(socket_buffer_full) {
      writeChannelKey = clientChannel.register(reactor.selector,SelectionKey.OP_WRITE,this)
    }
    else if(outBoundData.isEmpty) cancel_write()
  }

  // FIXME: sending large chunk of data like this can be problematic
  def send_data(faxData: String): Unit = {
    var buffer: ByteBuffer = ByteBuffer.allocate(faxData.length)
    buffer.put(faxData.getBytes("ISO-8859-1"))
    buffer.flip()
    outBoundData.add(buffer)
    write_and_schedule()
  }

  def connection_completed(): Unit = { }
  
  def add_timer(delay: Int)(timer_method: () => Unit) {
    reactor.add_timer(delay)(timer_method)
  }

  def add_periodic_timer(interval: Int)(timer_method: () => Unit) {
    reactor.add_periodic_timer(interval)(timer_method)
  }
}
